#include <iostream>
#include <unistd.h>
#include <vector>
#include <string>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"

using namespace std;

const int num = 5;
static int number = 1;

class channel
{
public:
    int _ctrlfd;   // 写端描述符
    int _workerid; // 对应写入的进程
    string _name;  // 管道的名称
public:
    channel(int ctrlfd, int workerid)
        : _ctrlfd(ctrlfd), _workerid(workerid)
    {
        _name = "channel-" + to_string(number++);
    }
};

void Work()
{
    while (true)
    {
        int command = 0;
        ssize_t n = read(0, &command, sizeof(command));

        if (!init.CheckSafe(command))
            continue;

        if (n == sizeof(command))
            init.RunTask(command);
        else if (n == 0)
            break;
        else
        {
            // nothing to do
        }
    }
    cout << "child quit" << endl;
}

// 传参形式建议
// 输入参数：const &
// 输出参数：*
// 输入输出参数：&

// 创建方式1会导致除最后一个创建的管道只有一个父进程的fd指向写端,一个子进程的fd指向读端
// 前面的所有管道都有一个子进程的fd指向读端，一个父进程的fd和多个子进程的fd指向写端
// 所以在回收资源的时候需要注意一下回收顺序
// void CreateChannels(vector<channel> *channels)
// {
//     // 定义并创建管道
//     for (int i = 0; i < num; i++)
//     {
//         int pipefd[2] = {0};
//         int n = pipe(pipefd);
//         assert(n == 0);
//         (void)n;

//         // 创建子进程

//         pid_t id = fork();
//         assert(id >= 0);

//         // 关闭不需要的fd，形成单向管道
//         if (id == 0)
//         {
//             // child

//             close(pipefd[1]);
//             dup2(pipefd[0], 0); // 这里输入重定向，就是为了让Work向0中读取，让Work少一个参数，仅此而已
//             Work();

//             exit(0);
//         }

//         // father
//         close(pipefd[0]);
//         channels->push_back(channel(pipefd[1], id));
//     }
// }

// 创建方式2保证了所有管道都有一个子进程的fd指向读端，一个父进程的fd和多个子进程的fd指向写端
void CreateChannels(vector<channel> *channels)
{
    vector<int> oldfd;
    // 定义并创建管道
    for (int i = 0; i < num; i++)
    {
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;

        // 创建子进程

        pid_t id = fork();
        assert(id >= 0);

        // 关闭不需要的fd，形成单向管道
        if (id == 0)
        {
            // child
            // 关闭当前文件的写端
            close(pipefd[1]);
            // 关闭之前管道文件的写端
            if(!oldfd.empty())
            {
                for(auto& fd : oldfd)
                {
                    close(fd);
                }
            }
            dup2(pipefd[0], 0); // 这里输入重定向，就是为了让Work向0中读取，让Work少一个参数，仅此而已
            Work();

            exit(0);
        }

        // father
        close(pipefd[0]);
        channels->push_back(channel(pipefd[1], id));
        oldfd.push_back(pipefd[1]);
    }
}

void Print(const vector<channel> &channels)
{
    for (const auto &channel : channels)
    {
        cout << channel._name << "  " << channel._ctrlfd << "  " << channel._workerid << endl;
    }
}

const bool g_always_loop = 1;

// 一直执行任务，则num为-1，否则num为执行任务的次数
void SendCommand(const vector<channel> &channels, int flag, int num = -1)
{
    int pos = 0;
    while (true)
    {
        // 1.选择任务 --- 随机
        int command = init.SelectTask();
        // 2.选择信道（进程）--- 轮询 --- 将任务较为平均的交给进程
        channel c = channels[pos++];
        pos %= channels.size();
        cout << "sent command " << init.ToDesc(command) << "[" << command << "]"
             << " in " << c._name << " worker is:" << c._workerid << endl;

        // 3.发送任务
        write(c._ctrlfd, &command, sizeof(command));

        // 4.判断是否退出
        if (flag != g_always_loop)
        {
            num--;
            if (num <= 0)
            {
                break;
            }
        }

        sleep(1);
    }

    cout << "SendCommand done..." << endl;
}

void ReleaseChannel(const vector<channel> &channels)
{
    // 每个管道都只有一个fd指向写端时，可以使用该方式回收
    for (const auto &channel : channels)
    {
        close(channel._ctrlfd);
        pid_t rid = waitpid(channel._workerid, nullptr, 0);
    }

    // 在多个fd指向读端的情况下，回收资源的方法2
    // 逆序边关闭写端，再回收对应的子进程
    // for (int i = channels.size() - 1; i >= 0; i--)
    // {
    //     close(channels[i]._ctrlfd);
    //     pid_t rid = waitpid(channels[i]._workerid, nullptr, 0);
    // }

    // 在多个fd指向读端的情况下，回收资源的方法1
    // 先将所有写端全部关闭，就可以随意回收子进程了
    // for (const auto &channel : channels)
    // {
    //     close(channel._ctrlfd);
    // }

    // for (const auto &channel : channels)
    // {
    //     pid_t rid = waitpid(channel._workerid, nullptr, 0);
    //     if (rid == channel._workerid)
    //     {
    //         cout << "wait child " << rid << " success" << endl;
    //     }
    // }
}

int main()
{
    vector<channel> channels;
    // 创建信道创建进程
    CreateChannels(&channels);

    // 开始完成任务
    // SendCommand(channels,g_always_loop);
    SendCommand(channels, !g_always_loop, 10);

    // 回收资源，释放管道，关闭写端，等待回收子进程
    ReleaseChannel(channels);

    // Print(channels);
    //  sleep(10);

    return 0;
}